/**
 * Copyright (c), 上海沃恩信息科技有限公司.
 * All rights reserved.
 * Licensed under BSD (https://www.pittypat.work/bsd.html).
 */

package pittypat.data;

import pittypat.FunctionArgs;
import pittypat.FunctionModel;
import pittypat.ParameterMeta;
import pittypat.primitives.Uuid;

import java.math.BigDecimal;
import java.sql.CallableStatement;
import java.sql.Date;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.sql.Time;
import java.sql.Timestamp;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.util.UUID;

/**
 * 针对 MySQL 数据库的数据服务。
 */
public final class MySqlService extends DbService {
	/**
	 * 使用连接字符串初始化 DataService 类的新实例。
	 * 
	 * @param connectionString
	 *            连接字符串。
	 * @throws IllegalArgumentException
	 *             connectionString 为 null 或空字符串。
	 */
	public MySqlService(String connectionString) {
		super(connectionString);
	}

	/**
	 * 为指定的命令创建一个参数。
	 * 
	 * @param statement
	 *            目标命令。
	 * @param meta
	 *            参数的元数据信息。
	 * @param position
	 *            参数的位置。
	 * @param args
	 *            参数值的列表。
	 * @throws SQLException
	 */
	@Override
	protected void createParameter(CallableStatement statement, ParameterMeta meta, int position, FunctionArgs args)
			throws SQLException {
		Object obj = args.getParameterValue(meta.getIdentifier());
		if (obj == null) {
			statement.setNull(position, this.getSqlType(meta));
			return;
		}

		Class<?> type = meta.getDataType();
		if (type == String.class) {
			statement.setString(position, (String) obj);
		} else if (type == Boolean.class) {
			statement.setBoolean(position, (Boolean) obj);
		} else if (type == Integer.class) {
			statement.setInt(position, (Integer) obj);
		} else if (type == Short.class) {
			statement.setShort(position, (Short) obj);
		} else if (type == Byte.class) {
			statement.setByte(position, (Byte) obj);
		} else if (type == Long.class) {
			statement.setLong(position, (Long) obj);
		} else if (type == LocalDateTime.class) {
			statement.setTimestamp(position, Timestamp.valueOf((LocalDateTime) obj));
		} else if (type == LocalDate.class) {
			statement.setDate(position, Date.valueOf((LocalDate) obj));
		} else if (type == LocalTime.class) {
			statement.setTime(position, Time.valueOf((LocalTime) obj));
		} else if (type == UUID.class) {
			// Pittypat 中，MySQL 数据库中使用 binary(16) 存储 UUID
			statement.setBytes(position, pittypat.primitives.Uuid.toBinary((UUID) obj));
		} else if (type == Float.class) {
			statement.setFloat(position, (Float) obj);
		} else if (type == Double.class) {
			statement.setDouble(position, (Double) obj);
		} else if (type == BigDecimal.class) {
			statement.setBigDecimal(position, (BigDecimal) obj);
		} else if (type == byte[].class) {
			statement.setBytes(position, (byte[]) obj);
		} else {
			statement.setObject(position, obj);
		}
	}

	/**
	 * 注册 _success 和 _message 输出参数。
	 * 
	 * @param statement
	 *            目标命令。
	 * @param position
	 *            参数的位置。
	 * @throws SQLException
	 */
	@Override
	protected void regSysParameters(CallableStatement statement, int position) throws SQLException {
		// _success
		statement.registerOutParameter(position, java.sql.Types.BOOLEAN);
		// _message
		statement.registerOutParameter(position + 1, java.sql.Types.LONGVARCHAR);
	}

	/**
	 * 获取指定参数的数据类型代码。
	 * 
	 * @param meta
	 *            参数的元数据信息。
	 * @return 数据库数据类型代码。
	 */
	@Override
	protected int getSqlType(ParameterMeta meta) {
		Integer code = meta.getSqlType();
		if (code != null) {
			return code;
		}

		Class<?> type = meta.getDataType();
		int len = meta.getMaxLength();
		if (type == String.class) {
			if (len > 0 && len <= 255) {
				code = meta.isFixedLength() ? java.sql.Types.CHAR : java.sql.Types.VARCHAR;
			} else {
				code = java.sql.Types.LONGVARCHAR;
			}
		} else if (type == Boolean.class) {
			code = java.sql.Types.BOOLEAN;
		} else if (type == Integer.class) {
			code = java.sql.Types.INTEGER;
		} else if (type == Short.class) {
			code = java.sql.Types.SMALLINT;
		} else if (type == Byte.class) {
			code = java.sql.Types.TINYINT;
		} else if (type == Long.class) {
			code = java.sql.Types.BIGINT;
		} else if (type == LocalDateTime.class) {
			code = java.sql.Types.TIMESTAMP;
		} else if (type == LocalDate.class) {
			code = java.sql.Types.DATE;
		} else if (type == LocalTime.class) {
			code = java.sql.Types.TIME;
		} else if (type == UUID.class) {
			code = java.sql.Types.BINARY;
		} else if (type == Float.class) {
			code = java.sql.Types.FLOAT;
		} else if (type == Double.class) {
			code = java.sql.Types.DOUBLE;
		} else if (type == BigDecimal.class) {
			code = java.sql.Types.DECIMAL;
		} else if (type == byte[].class) {
			if (len > 0 && len <= 255) {
				code = meta.isFixedLength() ? java.sql.Types.BINARY : java.sql.Types.VARBINARY;
			} else {
				code = java.sql.Types.LONGVARBINARY;
			}
		} else {
			code = java.sql.Types.LONGVARBINARY;
		}

		meta.setSqlType(code);
		return code;
	}

	/**
	 * 设置基础类型输出参数的值。
	 * 
	 * @param statement
	 *            目标数据库命令。
	 * @param meta
	 *            参数的元数据。
	 * @param position
	 *            参数的位置。
	 * @param args
	 *            参数集合。
	 * @throws SQLException
	 */
	@Override
	protected void setOutParameter(CallableStatement statement, ParameterMeta meta, int position, FunctionArgs args)
			throws SQLException {
		Class<?> type = meta.getDataType();
		if (type == String.class) {
			args.setParameterValue(meta.getIdentifier(), statement.getString(position));
		} else if (type == Boolean.class) {
			args.setParameterValue(meta.getIdentifier(), statement.getBoolean(position));
		} else if (type == Integer.class) {
			args.setParameterValue(meta.getIdentifier(), statement.getInt(position));
		} else if (type == Short.class) {
			args.setParameterValue(meta.getIdentifier(), statement.getShort(position));
		} else if (type == Byte.class) {
			args.setParameterValue(meta.getIdentifier(), statement.getByte(position));
		} else if (type == Long.class) {
			args.setParameterValue(meta.getIdentifier(), statement.getLong(position));
		} else if (type == LocalDateTime.class) {
			Timestamp ts = statement.getTimestamp(position);
			args.setParameterValue(meta.getIdentifier(), ts == null ? null : ts.toLocalDateTime());
		} else if (type == LocalDate.class) {
			Date date = statement.getDate(position);
			args.setParameterValue(meta.getIdentifier(), date == null ? null : date.toLocalDate());
		} else if (type == LocalTime.class) {
			Time time = statement.getTime(position);
			args.setParameterValue(meta.getIdentifier(), time == null ? null : time.toLocalTime());
		} else if (type == UUID.class) {
			// Pittypat 中，MySQL 数据库中使用 binary(16) 存储 UUID
			args.setParameterValue(meta.getIdentifier(), Uuid.fromBinary(statement.getBytes(position)));
		} else if (type == Float.class) {
			args.setParameterValue(meta.getIdentifier(), statement.getFloat(position));
		} else if (type == Double.class) {
			args.setParameterValue(meta.getIdentifier(), statement.getDouble(position));
		} else if (type == BigDecimal.class) {
			args.setParameterValue(meta.getIdentifier(), statement.getBigDecimal(position));
		} else if (type == byte[].class) {
			args.setParameterValue(meta.getIdentifier(), statement.getBytes(position));
		} else {
			args.setParameterValue(meta.getIdentifier(), statement.getObject(position));
		}
	}

	/**
	 * 设置 _success 和 _message 输出参数的值。
	 * 
	 * @param statement
	 *            目标命令。
	 * @param position
	 *            参数的位置。
	 * @param model
	 *            功能模型。
	 * @throws SQLException
	 */
	@Override
	protected void setSysParameterValues(CallableStatement statement, int position, FunctionModel<?> model) throws SQLException {
		model.setSuccess(statement.getBoolean(position));
		model.setMessage(statement.getString(position + 1));
	}

	/**
	 * 实现 DbDataReader，从 MySQL 数据库中读取一个或多个结果集合的数据行。
	 */
	private final class MySqlDataReader extends DbDataReader {
		/**
		 * 使用目标数据集合初始化 MySqlDataReader 类的新实例。
		 * 
		 * @param statement
		 *            目标数据库命令。
		 * @throws SQLException
		 */
		public MySqlDataReader(Statement statement) throws SQLException {
			super(statement);
		}

		/**
		 * 从数据行中获取一个 UUID 值。
		 * 
		 * @param resultSet
		 *            结果集合。
		 * @param name
		 *            列名称。
		 * @return 一个 UUID 值，如果无法转换为 UUID，则返回 null。
		 * @throws SQLException
		 */
		@Override
		protected UUID getUuid(ResultSet resultSet, String name) throws SQLException {
			int index = resultSet == null ? 0 : resultSet.findColumn(name);

			return index < 1 ? null : Uuid.fromBinary(resultSet.getBytes(index));
		}
	}

	/**
	 * 创建一个新的 DbDataReader 对象。
	 * 
	 * @param statement
	 *            目标数据库命令。
	 * @return 一个 DbDataReader 对象实例。
	 * @throws SQLException
	 */
	@Override
	protected DbDataReader createDataReader(CallableStatement statement) throws SQLException {
		return new MySqlDataReader(statement);
	}
}
